/************************************************************************************
*    File Name:  spi_master.v  
*      Version:  1.0
*      Created:  2009-07-01
*       Author:  sxu
*        Email:  sxu@vastdigital.com
*  Description:	 This module is used to communicate with Winbond's spi flash:
                 W25X10,W25X20,W25X40 and W25X80.
                 
*    Character: o_spi_clk's current frequency is 1/4 of i_clk
                support four commands:  
                                        8'h01: write status register
                                        8'h03: read flash data 
                                        8'h02: write flash data (one page each time)

*  Rev   Author              Date        Changes                              																																																											
*  ----  -------------   ----------   -----------------------																																																										
*  1.0   sxu              07/01/2009  
*  1.1	 chenyan	  09/16/2010            

************************************************************************************/
`timescale 1ns/1ns
module spi_master#(
		parameter PAGE_ADDR_WIDTH = 16,
   		parameter DATA_WIDTH      = 8,
   		parameter PAGE_CAPABILITY = 256*8,
   		parameter OCLK_DIV_NUM = 5)	//125MHz/5=25MHz
   		(
		input	wire				i_clk,          //system clock , 125MHz
		input	wire				i_rst_n,        //system reset , active low , 

		input	wire				i_work_start,   //operation start flag , active high , 1 clock period
		input	wire	[1:0]			iv_work_mode,    //2'b01:write data,2'b10:read data,2'b11:write status data
		input 	wire 	[PAGE_ADDR_WIDTH-1:0]	iv_work_page,    //page address to be read/written, 16bits
		input	wire				i_stop_rd,      //stop read data from flash

		output	reg				o_w_r_busy,     //the module is busy, any operation is forbidden
		input 	wire 	[DATA_WIDTH-1:0]	iv_wr_data,      //write data , 8 bits
		output	wire				o_wr_ack,       //write answer flag , new data should be input at the next clock period
		
		output 	reg  	[DATA_WIDTH-1:0]	ov_rd_data_out,  //read data, 8 bits
  		output	wire				o_rd_dv,        //read data valid
		input	wire	[3:0]			mem_w_page,	//version 1.1 by chenyan
		input	wire	[7:0]			page_waddr,		//version 1.1 by chenyan
		input	wire	[7:0]			page_raddr,		//version 1.1 by chenyan
		//flash interface
	 	output	reg				o_spi_clk,
		output	reg				o_spi_cs_n,
		output	wire				o_spi_si,
		input	wire				i_spi_so
		);

   


////////////////////////////////////////////////////
//instruction
  parameter	C_WR_ENABLE		= 8'h06;
  parameter	C_WR_DISABLE		= 8'h04;
  parameter	C_RD_STA_REG		= 8'h05;
  parameter	C_WR_STA_REG		= 8'h01;
  parameter	C_RD_DATA		= 8'h03;
  parameter	C_FAST_RD		= 8'h0B;
  parameter	C_PAGE_PROGRAM		= 8'h02;
  parameter	C_BLOCK_ERASE		= 8'hD8;//8'hC7;
  parameter	C_SECTOR_ERASE		= 8'h20;//8'hD8;
  parameter	C_CHIP_ERASE		= 8'hC7;
//main state
  parameter IDLE_STATE       = 13'b0_0000_0000_0001;
  parameter R_CMD_STATE      = 13'b0_0000_0000_0010;
  parameter R_DATA_STATE     = 13'b0_0000_0000_0100;
  parameter WRITE_ENABLE     = 13'b0_0000_0000_1000;
  parameter SECTOR_ERASE_CMD = 13'b0_0000_0001_0000;
  parameter ERASE_WAIT       = 13'b0_0000_0010_0000;
  parameter W_WRITE_ENABLE   = 13'b0_0000_0100_0000;
  parameter W_CMD_STATE      = 13'b0_0000_1000_0000;
  parameter W_DATA_STATE     = 13'b0_0001_0000_0000;
  parameter W_WAIT           = 13'b0_0010_0000_0000;
  parameter W_STATUS_ENABLE  = 13'b0_0100_0000_0000;
  parameter W_STATUS_STATE   = 13'b0_1000_0000_0000;
  parameter W_STATUS_WAIT    = 13'b1_0000_0000_0000;
  
  
  reg [12:0] main_state; //state register of main FSM
  reg [12:0] main_state_t; 
  reg oclk_en     ;  //flash SPI SCK enable
  reg [2:0] oclk_div_count; //SPI SCK generator , 1/8 times divider
  wire shift_en;    //SPI SI out ,1 clock pulse when meet negedge of flash_SCK  
  reg load_data;
  wire dly_end;
  reg data_valid;
  reg load_data_en;
  reg load_cmd;  //load command words and address enable
  reg [PAGE_ADDR_WIDTH-1:0] work_page_buf;   //page register
  reg [1:0]work_mode_buf;       //mode register , 1 write mode , 0 read mode 
  reg [3:0] dly_cnt;       //delay 100ns for o_spi_cs_n high bettwen instructions
  reg dly_cnt_en;
  reg oclk_end;
  wire oclk_end_pre;
  reg [3:0] dly_cnt_idle;  //delay 100ns for o_spi_cs_n high from page program/read/write status reg to IDLE state
  reg dly_cnt_idle_en;
  reg enable_end;
  reg w_cmd_end;
  reg write_end;
  reg wait_end;
  reg erase_cmd_end;
  reg r_cmd_end;
  reg wr_status_end;
  reg stop_rd_t;
  reg [31:0] shift_out_reg;   //shift out cmd /data 
  reg	[11:0]	current_sector;
  reg current_sector_is_erased;
  reg [31:0] spi_bit_cnt; //SPI counter, record how many bits have been sent/received
  reg spi_cs_high;
  reg spi_cs_low;
  
  
//version 1.1 by chenyan
reg	[8:0]	mem_w_num;
reg	[7:0]	page_waddr_reg;
reg	[7:0]	page_raddr_reg;
  
always@(posedge i_clk or negedge i_rst_n)
	if(i_rst_n==0)
	  	mem_w_num <= 9'h1FF;
	else case(mem_w_page)
	  	3'd0 : 	mem_w_num <= 9'h040;
	  	3'd1 : 	mem_w_num <= 9'h080;
	  	3'd2 : 	mem_w_num <= 9'h0c0;
	  	3'd3 : 	mem_w_num <= 9'h100;
	  	default:mem_w_num<= 9'h0;
	endcase

always @ (posedge i_clk or negedge i_rst_n)
  	if (i_rst_n == 0)	begin
    		page_waddr_reg <= 0;
    		page_raddr_reg <= 0;
    	end
  	else if (i_work_start)	begin
    		page_waddr_reg <= page_waddr;
    		page_raddr_reg <= page_raddr;
    	end
////////////main state machine/////////////////////////////////

always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		main_state <= IDLE_STATE;
	else
		case (main_state)
		  IDLE_STATE:
				if (i_work_start == 1) 
					 begin
					   if (iv_work_mode==2'b01)//write data
						   main_state <= WRITE_ENABLE;
					   else if (iv_work_mode==2'b10)//read data
						   main_state <= R_CMD_STATE;
						 else if (iv_work_mode==2'b11)//write status reg
						 	 main_state <= W_STATUS_ENABLE;
					 end
				else
					main_state <= IDLE_STATE;	
			WRITE_ENABLE:
				if (enable_end == 1)
					 begin
					   if (current_sector_is_erased == 1)	
					   	 main_state <= W_CMD_STATE;
					   else
					   	 main_state <= SECTOR_ERASE_CMD; 
					 end
				else
					main_state <= WRITE_ENABLE;
			W_CMD_STATE:
				if (w_cmd_end== 1)
					main_state <= W_DATA_STATE;
				else
					main_state <= W_CMD_STATE;
			W_DATA_STATE:
				if (write_end == 1)	
					main_state <= W_WAIT;
				else
					main_state <= W_DATA_STATE;
			W_WAIT:
				if (wait_end == 1)	
					main_state <= IDLE_STATE;
				else
					main_state <= W_WAIT;
			SECTOR_ERASE_CMD:
				if (erase_cmd_end == 1)	
					main_state <= ERASE_WAIT;
				else
					main_state <= SECTOR_ERASE_CMD;					
			ERASE_WAIT:	
				if (wait_end == 1)
					main_state <= W_WRITE_ENABLE;
				else
					main_state <= ERASE_WAIT;
			W_WRITE_ENABLE:
				if (enable_end==1)
					main_state <= W_CMD_STATE;
				else
					main_state <= W_WRITE_ENABLE;
			R_CMD_STATE:
				if (r_cmd_end == 1)	
					main_state <= R_DATA_STATE;
				else
					main_state <= R_CMD_STATE;					
			R_DATA_STATE:
				if (i_stop_rd == 1'b1)	
					main_state <= IDLE_STATE;
				else
					main_state <= R_DATA_STATE;
			W_STATUS_ENABLE:
			  if (enable_end==1)
			  	main_state <= W_STATUS_STATE;
				else
					main_state <= W_STATUS_ENABLE;
			W_STATUS_STATE:
			  if (wr_status_end==1)
			  	main_state <= W_STATUS_WAIT;
				else
					main_state <= W_STATUS_STATE;
			W_STATUS_WAIT:
			  if (wait_end == 1)
					main_state <= IDLE_STATE;
				else
					main_state <= W_STATUS_WAIT;							
			default:
				main_state <= IDLE_STATE;
		endcase	

///////////////////////////////////////////////
always @(posedge i_clk)
   main_state_t<=main_state;

always@(posedge i_clk or negedge i_rst_n)
	if(i_rst_n == 0)
		current_sector <= 0;
	else if(erase_cmd_end)
		current_sector <= work_page_buf[PAGE_ADDR_WIDTH-1:4];

always@(posedge i_clk or negedge i_rst_n)
	if(i_rst_n == 0)
		current_sector_is_erased <= 0;
	else if (main_state==ERASE_WAIT && wait_end)
		current_sector_is_erased <= 1;
	else if (i_work_start && iv_work_page[PAGE_ADDR_WIDTH-1:4]!=current_sector)
		current_sector_is_erased <= 0;

//i_clk divider 
always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		oclk_div_count <= 0;
	else if (oclk_en == 0 || oclk_div_count == OCLK_DIV_NUM-1)
		oclk_div_count<=0;
	else
		oclk_div_count<=oclk_div_count+1;
//
always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		oclk_end <= 0;
	else if (oclk_div_count == OCLK_DIV_NUM-2 && oclk_en)
		oclk_end <= 1;
	else
		oclk_end <= 0;

assign oclk_end_pre=oclk_div_count == OCLK_DIV_NUM-2 && oclk_en;

//out i_clk count enable

always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		oclk_en <= 0;
	else if (load_cmd)
		oclk_en <= 1;	
	else 
		case(main_state)			
				WRITE_ENABLE, 
				W_WRITE_ENABLE,
				W_STATUS_ENABLE: 
				  if (spi_bit_cnt[2:0]==3'b111 && oclk_end)
				  	oclk_en <= 0;
				W_WAIT,
				ERASE_WAIT,
				W_STATUS_WAIT:
				  if (data_valid && ov_rd_data_out[0] == 0)
				  	oclk_en <= 0;
				W_DATA_STATE:
				  //if (spi_bit_cnt == PAGE_CAPABILITY+31 && oclk_end)
				  if (spi_bit_cnt == {mem_w_num,3'b0}+31 && oclk_end)
				  	oclk_en <= 0;
				W_STATUS_STATE:
				  if (spi_bit_cnt[3:0] == 4'b1111 && oclk_end)
				  	oclk_en <= 0;
				SECTOR_ERASE_CMD:
				  if (spi_bit_cnt[4:0]==5'b11111 && oclk_end)
				  	oclk_en <= 0;
				R_DATA_STATE:
				  if (i_stop_rd)  
				  	oclk_en <= 0;	
							
		endcase
		
		

always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		enable_end <= 0;
	else if ((main_state == WRITE_ENABLE || main_state == W_WRITE_ENABLE || main_state == W_STATUS_ENABLE)&& spi_bit_cnt[2:0]==3'b111 && oclk_end )
		enable_end <= 1;
	else 
		enable_end <= 0;

always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		dly_cnt_en <=0;
	else if (dly_cnt == 10)
		dly_cnt_en <=0;
	else if (enable_end || erase_cmd_end || write_end || wr_status_end || main_state == ERASE_WAIT && wait_end)
		dly_cnt_en <= 1;
//delay 100ns for o_spi_cs_n high bettwen instructions
always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		dly_cnt <= 0;
	else if (dly_cnt_en)
		dly_cnt <= dly_cnt + 1;
	else
		dly_cnt <= 0;

assign  dly_end = (dly_cnt == 10);

always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		erase_cmd_end <= 0;
	else if (main_state == SECTOR_ERASE_CMD && spi_bit_cnt[4:0]==5'b11111 && oclk_end )
		erase_cmd_end <= 1;
	else 
		erase_cmd_end <= 0;

always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		w_cmd_end <= 0;
	else if (main_state == W_CMD_STATE && spi_bit_cnt[4:0]==5'b11111 && oclk_end )
		w_cmd_end <= 1;
	else 
		w_cmd_end <= 0;

always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		r_cmd_end <= 0;
	else if (main_state == R_CMD_STATE && spi_bit_cnt[4:0]==5'b11111 && oclk_end )
		r_cmd_end <= 1;
	else 
		r_cmd_end <= 0;
		
always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		write_end <= 0;
	//else if (main_state == W_DATA_STATE && spi_bit_cnt == PAGE_CAPABILITY+31 && oclk_end)
	else if (main_state == W_DATA_STATE && spi_bit_cnt == {mem_w_num,3'b0}+31 && oclk_end)
		write_end <= 1;
	else
		write_end <= 0;

always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		wait_end <= 0;
	else if ((main_state == W_WAIT || main_state == ERASE_WAIT || main_state == W_STATUS_WAIT) && data_valid)
		begin
		  if (ov_rd_data_out[0] == 0)//status reg bit0 indicate flash is busy or not;1 busy , 0 idle
		    wait_end <= 1;
		  else
		  	wait_end <= 0;
	  end
	else
		wait_end <= 0;

always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		wr_status_end <= 0;
	else if (main_state == W_STATUS_STATE && spi_bit_cnt[3:0]==4'b1111 && oclk_end )
		wr_status_end <= 1;
	else 
		wr_status_end <= 0;
		
////////////////////////////////////////////////
always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		load_cmd <= 0;
	else if (main_state == WRITE_ENABLE && main_state_t != WRITE_ENABLE || main_state == R_CMD_STATE && main_state_t != R_CMD_STATE || dly_end )
		load_cmd <= 1;
	else if (main_state == W_STATUS_ENABLE && main_state_t != W_STATUS_ENABLE)
		load_cmd <= 1;
	else
		load_cmd <= 0;

always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		load_data_en <= 0;
	//else if (main_state == W_DATA_STATE && (spi_bit_cnt >= 31 && spi_bit_cnt < PAGE_CAPABILITY+31) || main_state == W_STATUS_STATE && spi_bit_cnt[3]==0 || main_state == W_CMD_STATE && spi_bit_cnt == 31)
	else if (main_state == W_DATA_STATE && (spi_bit_cnt >= 31 && spi_bit_cnt < {mem_w_num,3'b0}+31) || main_state == W_STATUS_STATE && spi_bit_cnt[3]==0 || main_state == W_CMD_STATE && spi_bit_cnt == 31)
		load_data_en <= 1;
	else
		load_data_en <= 0;

always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		load_data <= 0;
	else if (load_data_en && oclk_end_pre && work_mode_buf[0]==1 && spi_bit_cnt[2:0]==3'b111)
		load_data <= 1;
	else
		load_data <= 0;
//assign  load_data = load_data_en && oclk_end && work_mode_buf[0]==1 && spi_bit_cnt[2:0]==3'b111 ;
assign  o_wr_ack = load_data;

always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		shift_out_reg <= 0;
	else if (load_cmd)//write
		begin
			case (main_state)
				R_CMD_STATE:      shift_out_reg <= {C_RD_DATA,work_page_buf,page_raddr_reg};
				WRITE_ENABLE:     shift_out_reg <= {C_WR_ENABLE,24'b0};
				SECTOR_ERASE_CMD: shift_out_reg <= {C_SECTOR_ERASE,work_page_buf[PAGE_ADDR_WIDTH-1:4],12'b0};
				ERASE_WAIT:       shift_out_reg <= {C_RD_STA_REG,24'b0};
				W_WRITE_ENABLE:   shift_out_reg <= {C_WR_ENABLE,24'b0};
				W_CMD_STATE:      shift_out_reg <= {C_PAGE_PROGRAM,work_page_buf,page_waddr_reg};
				W_WAIT:           shift_out_reg <= {C_RD_STA_REG,24'b0};
				W_STATUS_ENABLE:  shift_out_reg <= {C_WR_ENABLE,24'b0};
				W_STATUS_STATE:   shift_out_reg <= {C_WR_STA_REG,24'b0};
				W_STATUS_WAIT:    shift_out_reg <= {C_RD_STA_REG,24'b0};
				default:          shift_out_reg <= 0;
			endcase
		end
	else if (load_data)
		shift_out_reg <= {iv_wr_data,24'h0};
	else if (shift_en)
		shift_out_reg <= shift_out_reg << 1;

always @ (posedge i_clk or negedge i_rst_n)
  if (i_rst_n == 0)
    work_page_buf <= 0;
  else if (i_work_start)
    work_page_buf <= iv_work_page;
      
always @ (posedge i_clk or negedge i_rst_n)
  if (i_rst_n == 0)
    work_mode_buf <= 0;
  else if (i_work_start)
    work_mode_buf <= iv_work_mode; 

always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		o_w_r_busy <= 0;
	else if (dly_cnt_idle==10)
		o_w_r_busy <= 0;
  else if (!(main_state == IDLE_STATE))
  	o_w_r_busy <= 1;

always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		dly_cnt_idle_en <=0;
	else if (dly_cnt_idle == 10)
		dly_cnt_idle_en <=0;
	else if ((main_state == W_WAIT || main_state == W_STATUS_WAIT)&& wait_end || i_stop_rd)
		dly_cnt_idle_en <= 1;

//delay 100ns for o_spi_cs_n high from page program/read/write status reg to IDLE state
always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		dly_cnt_idle <= 0;
	else if (dly_cnt_idle_en)
		dly_cnt_idle <= dly_cnt_idle + 1;
	else
		dly_cnt_idle <= 0;

///////////////////////////////////////////////

assign shift_in = (oclk_div_count == OCLK_DIV_NUM/2-1);
assign  shift_en = oclk_end;

always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		spi_bit_cnt <= 0;
	else if (main_state == IDLE_STATE || enable_end || erase_cmd_end || write_end || wait_end || wr_status_end)
		spi_bit_cnt <= 0;
	else if (shift_en)
		spi_bit_cnt <= spi_bit_cnt + 1;

always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		data_valid <=1'b0;
	else if ((main_state == R_DATA_STATE || main_state == ERASE_WAIT || main_state == W_WAIT || main_state == W_STATUS_WAIT) && (oclk_end_pre && spi_bit_cnt>7 && spi_bit_cnt[2:0]==3'b111))
		data_valid <=1'b1;
	else 
		data_valid <=1'b0;

assign o_rd_dv = data_valid && main_state == R_DATA_STATE	;


always @ (posedge i_clk or negedge i_rst_n)
  if (i_rst_n == 0)
    ov_rd_data_out <= 0;
  else if (shift_in)
   ov_rd_data_out <= {ov_rd_data_out[6:0],i_spi_so};
///////////////////////////////////////////
always @(posedge i_clk or negedge i_rst_n)
  if (i_rst_n == 0)
    stop_rd_t<=0;
  else
   stop_rd_t<=i_stop_rd;
   
always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		o_spi_cs_n <=1'b1;
	else if (spi_cs_high)
		o_spi_cs_n <=1'b1;
	else if (spi_cs_low)
		o_spi_cs_n <=1'b0;

always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		spi_cs_high <=1'b0;
	else if (enable_end ||erase_cmd_end || wait_end || write_end || wr_status_end || stop_rd_t)
		spi_cs_high <=1'b1;
	else
		spi_cs_high <=1'b0;

always @(posedge i_clk or negedge i_rst_n)
	if (i_rst_n == 0)
		spi_cs_low <=1'b0;
	else if (main_state == WRITE_ENABLE || main_state == W_STATUS_ENABLE || main_state == R_CMD_STATE || dly_end)
		spi_cs_low <=1'b1;
	else
		spi_cs_low <=1'b0;



assign  o_spi_si  = shift_out_reg[31];

always @(posedge i_clk or negedge i_rst_n)
  if (i_rst_n == 0)
		o_spi_clk <=1'b0;
  else if (oclk_div_count == OCLK_DIV_NUM-1 || oclk_en == 0)
  	o_spi_clk <=1'b0;
  else if (oclk_div_count == OCLK_DIV_NUM/2-1)
  	o_spi_clk <=1'b1;

endmodule		
		